﻿using System;
using System.Collections.Generic;

namespace HappyTemplate
{
	public class ConversionException : ApplicationException
	{
		private ConversionException(string msg)
			: base(msg) {}

		//private ConversionException(string msg, Exception inner)
		//    : base(msg, inner)
		//{

		//}

		public static ConversionException NewThrowConversionFailed(Type fromType, object fromValue, Type toType,
		                                                           Exception inner)
		{
			throw new ConversionException(String.Format(Resources.MiscMessages.ConversionFailed_Value_FromType_ToType,
			                                            fromValue.ToString().Substring(0, 32), fromType, toType, inner));
		}

		public static void ThrowNoConversionExists(Type fromType, Type toType)
		{
			throw new ConversionException(String.Format(Resources.MiscMessages.NoConversionExists_FromType_ToType,
				fromType, toType));
		}
	}

	internal class OmniConvert
	{
		public static object ChangeType(object value, Type toType)
		{
			//if (!value.GetType().IsPrimitive)
			//    throw new ArgumentException(Resources.MiscMessages.ValueToConvertIsNotPrimitive, "value");

			//if (!toType.IsPrimitive)
			//    throw new ArgumentException(String.Format(Resources.MiscMessages.IsNotPrimitive_Type, toType));

			try
			{
				return Convert.ChangeType(value, toType);
			}
			catch (Exception e)
			{
				throw ConversionException.NewThrowConversionFailed(value.GetType(), value, toType, e);
			}
		}
	}

	public class OmniCompare
	{
		public static Type[] GetComparableTypes()
		{
			List<Type> types = new List<Type>();
			for (int i = 0; i < (int)TypeCode.EndOfTypeCodes; ++i)
				types.Add(GetType((TypeCode)i));

			return types.ToArray();
		}

		internal static int Compare(object obj1, object obj2)
		{
			if (obj1.GetType() != obj2.GetType() || !typeof (IComparable).IsInstanceOfType(obj1))
			{
				if (obj1.GetType().IsPrimitive && obj2.GetType().IsPrimitive)
				{
					//obj2 = OmniConvert.ChangeType(obj2, obj1.GetType());
					ConvertToCompatibleType(obj1, obj2, out obj1, out obj2);
				}
					//ConversionException.ThrowNoConversionExists(obj1.GetType(), obj2.GetType());
				else
				{
					obj1 = obj1.ToString();
					obj2 = obj2.ToString();
				}
			}

			return ((IComparable) obj2).CompareTo(obj1);
		}

		private static Type GetCompatibleType(Type type1, Type type2)
		{
			TypeCode typeCode1 = GetTypeCode(type1);
			TypeCode typeCode2 = GetTypeCode(type2);

			HashSet<Type> compatibleTypes1 = _conversionMatrix[type1];
			HashSet<Type> compatibleTypes2 = _conversionMatrix[type2];

			int bestMatch = (int)typeCode1 < (int)typeCode2 ? (int)typeCode1 : (int)typeCode2;
			Type bestMatchType = GetType((TypeCode) bestMatch);
			for (; bestMatch < (int)TypeCode.EndOfTypeCodes; ++bestMatch, bestMatchType = GetType((TypeCode)bestMatch))
				if (compatibleTypes1.Contains(bestMatchType) && compatibleTypes2.Contains(bestMatchType))
					break;

			if(bestMatch >= (int)TypeCode.EndOfTypeCodes)
				ConversionException.ThrowNoConversionExists(type1, type2);

			return bestMatchType;
		}

		internal enum TypeCode
		{
			//Order is very significant!
			Boolean,
			Byte,
			SByte,
			Int16,
			Char,
			UInt16,
			Int32,
			UInt32,
			Int64,
			UInt64,
			Single,
			Double,
			Decimal,
			String,
			EndOfTypeCodes
		}

		private static TypeCode GetTypeCode(Type type)
		{
			return (TypeCode)Enum.Parse(typeof(TypeCode), type.Name);
		}

		private static Type GetType(TypeCode typeCode)
		{
			switch (typeCode)
			{
				case TypeCode.Boolean:
					return typeof (Boolean);
				case TypeCode.Byte:
					return typeof (Byte);
				case TypeCode.SByte:
					return typeof (SByte);
				case TypeCode.Int16:
					return typeof (Int16);
				case TypeCode.Char:
					return typeof (Char);
				case TypeCode.UInt16:
					return typeof (UInt16);
				case TypeCode.Int32:
					return typeof (Int32);
				case TypeCode.UInt32:
					return typeof (UInt32);
				case TypeCode.Int64:
					return typeof (Int64);
				case TypeCode.UInt64:
					return typeof (UInt64);
				case TypeCode.Single:
					return typeof (Single);
				case TypeCode.Double:
					return typeof (Double);
				case TypeCode.Decimal:
					return typeof (Decimal);
				case TypeCode.String:
					return typeof (String);
				default:
					throw new UnhandledCaseException();
			}
		}

		private static void ConvertToCompatibleType(object input1, object input2, out object output1, out object output2)
		{
			Type compatibleType = GetCompatibleType(input1.GetType(), input2.GetType());
			output1 = OmniConvert.ChangeType(input1, compatibleType);
			output2 = OmniConvert.ChangeType(input2, compatibleType);
		}

		private static readonly Dictionary<Type, HashSet<Type>> _conversionMatrix
			= new Dictionary<Type, HashSet<Type>>
			  {
			  	{
			  		typeof (Boolean), 
					new HashSet<Type>
					  {
              			typeof (Byte),
              			typeof (SByte),
              			typeof (Int16),
              			typeof (Char),
              			typeof (UInt16),
              			typeof (Int32),
              			typeof (UInt32),
              			typeof (Int64),
              			typeof (UInt64),
              			typeof (Single),
              			typeof (Double),
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
				{
			  	   	typeof(Byte),
					new HashSet<Type>
					{
              			typeof (Int16),
              			typeof (Char),
              			typeof (UInt16),
              			typeof (Int32),
              			typeof (UInt32),
              			typeof (Int64),
              			typeof (UInt64),
              			typeof (Single),
              			typeof (Double),
              			typeof (Decimal),
              			typeof (String)
					}

				},
			  	{
			  		typeof(SByte), 
					new HashSet<Type>
					  {
              			typeof (Int16),
              			typeof (Int32),
              			typeof (Int64),
              			typeof (Single),
              			typeof (Double),
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (Int16), 
					new HashSet<Type>
					  {
              			typeof (Int32),
              			typeof (Int64),
              			typeof (Single),
              			typeof (Double),
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (Char), 
					new HashSet<Type>
					  {
						//typeof (UInt16),
						//typeof (Int32),
						//typeof (UInt32),
						//typeof (Int64),
						//typeof (UInt64),
						//typeof (Single),
						//typeof (Double),
						//typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (UInt16), 
					new HashSet<Type>
					  {
              			typeof (Char),
              			typeof (UInt16),
              			typeof (Int32),
              			typeof (UInt32),
              			typeof (Int64),
              			typeof (UInt64),
              			typeof (Single),
              			typeof (Double),
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (Int32), 
					new HashSet<Type>
					  {
              			typeof (Int64),
              			typeof (UInt64),
              			typeof (Single),
              			typeof (Double),
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (UInt32), 
					new HashSet<Type>
					  {
              			typeof (UInt64),
						typeof (Single),
              			typeof (Double),
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (Int64), 
					new HashSet<Type>
					  {
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (UInt64), 
					new HashSet<Type>
					  {
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (Single), 
					new HashSet<Type>
					  {
              			typeof (Double),
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 

				{
			  		typeof (Double), 
					new HashSet<Type>
					  {
              			typeof (Decimal),
              			typeof (String)
					  }
			  	}, 
			  	{
			  		typeof (Decimal), 
					new HashSet<Type>
					  {
              			typeof (String)
					  }
			  	}
			  };
	}
}